﻿// Inner Fire 游戏引擎库
// DirectX窗口类
//
// Copyright (c) 2023 by 尔西大帝. All rights reserved.
//
// 这个类专用于DirectX的Window类。
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-11-15

#include "dx_window.h"
#include <timer.h>

using namespace DirectX;

namespace ifire {

// 这东西临时用一下
#define IDC_DX12 109

DxWindow* DxWindow::dx_app_instance_ = nullptr;

static LRESULT CALLBACK DxWndProc(
    HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
  auto app = DxWindow::GetApp();
  if (app) {
    return app->WndProc(hwnd, msg, wParam, lParam);
  } else {
    return DefWindowProc(hwnd, msg, wParam, lParam);
  }
}

DxWindow::DxWindow()
    : app_exited_(false), created_(false), is_resizing_(false) {}

DxWindow::~DxWindow() {}

bool DxWindow::OpenWindow(HWND parent, RECT rect) {
  parent_ = parent;

  // 重置DirectX
  dx_ = std::make_unique<DxGraphics>();
  dx_->SetScene(scene_);

  // 根据传入的工作区大小，计算整个窗口需要的大小
  client_width_ = rect.right;
  client_height_ = rect.bottom;
  RECT adjust_rect = {0, 0, static_cast<LONG>(client_width_),
      static_cast<LONG>(client_height_)};
  AdjustWindowRect(&adjust_rect, WS_OVERLAPPEDWINDOW, false);
  width_ = adjust_rect.right - adjust_rect.left;
  height_ = adjust_rect.bottom - adjust_rect.top;

  // 创建窗口
  hwnd_ = CreateWindowEx(0, DX_WINDOW_CLASS_NAME, DX_WINDOW_NAME,
      WS_OVERLAPPEDWINDOW, rect.left, rect.top, width_, height_, parent_,
      nullptr, GetModuleHandle(NULL), nullptr);
  if (!hwnd_) {
    return false;
  }

  // 设置创建标志（重要）
  created_ = true;
  dx_app_instance_ = this;

  // 显示窗口
  ShowWindow(hwnd_, SW_SHOW);
  UpdateWindow(hwnd_);

  // 初始化DirectX
  dx_->InitDirect3D(hwnd_, client_width_, client_height_);
  // 通过OnResize重建SwapChain
  dx_->OnResize();
  // 初始化Pipeline
  dx_->InitPipeline();

  KLOG("Dx Window created!");

  // dx_->Test_Draw();

  return true;
}

void DxWindow::CheckCompileShader() {
  try {
    ComPtr<ID3DBlob> vertex_shader = dxutils::compile_shader(
        "shaders/VertexShader.hlsl", nullptr, "VS", "vs_5_0");
  } catch (ifire::DxException e) {
    OutputDebugStringA(e.ToString().c_str());
  }
  try {
    ComPtr<ID3DBlob> pixel_shader = dxutils::compile_shader(
        "shaders/PixelShader.hlsl", nullptr, "PS", "ps_5_0");
  } catch (ifire::DxException e) {
    OutputDebugStringA(e.ToString().c_str());
  }
}

bool DxWindow::Run() {
  if (!created_) {
    return false;
  }

  g_timer.Tick();

  if (!app_paused_) {
    if (g_timer.CheckFixedUpdate()) {
      g_timer.CalculateFps();
      Update();
      return true;
    }
  } else {
    Sleep(10);
  }

  return false;
}

void DxWindow::Update() {
  dx_->Update();
  SetTitle(dx_->GetTitle());
  dx_->Draw();
}


ATOM DxWindow::RegisterWinClass() {
  WNDCLASSEX wcex = {};

  wcex.cbSize = sizeof(WNDCLASSEX);

  wcex.style = CS_HREDRAW | CS_VREDRAW;
  wcex.lpfnWndProc = DxWndProc;
  wcex.hInstance = GetModuleHandle(NULL);
  wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
  wcex.lpszClassName = DX_WINDOW_CLASS_NAME;

  return RegisterClassEx(&wcex);
}

void DxWindow::OnMouseDown(WPARAM btn_state, int x, int y) {
  dx_->UpdateCamera_OnMouseDown(btn_state, x, y);
  dx_->Scene_OnMouseDown(btn_state, x, y);
}

void DxWindow::OnMouseUp(WPARAM btn_state, int x, int y) {
  dx_->UpdateCamera_ReleaseCapture();
  dx_->Scene_OnMouseUp(btn_state, x, y);
}

void DxWindow::OnMouseMove(WPARAM btn_state, int x, int y) {
  dx_->UpdateCamera_OnMouseMove(btn_state, x, y);
  dx_->Scene_OnMouseMove(btn_state, x, y);
}

void DxWindow::OnResize(WPARAM wParam, LPARAM lParam) {
  // 重置窗口
  dx_->SetClientSize(LOWORD(lParam), HIWORD(lParam));
  // 更新投影矩阵
  dx_->UpdateProjection();

  // 如果没有初始化，则跳过
  if (!dx_->IsReady()) {
    return;
  }

  // 根据情况进行更改
  if (wParam == SIZE_MINIMIZED) {
    app_paused_ = true;
    minimized_ = true;
    maximized_ = false;
  } else if (wParam == SIZE_MAXIMIZED) {
    app_paused_ = false;
    minimized_ = false;
    maximized_ = true;
    dx_->OnResize();
  } else if (wParam == SIZE_RESTORED) {
    // 从最小化恢复?
    if (minimized_) {
      app_paused_ = false;
      maximized_ = false;
      dx_->OnResize();
    }
    // 还是从最大化恢复?
    else if (maximized_) {
      app_paused_ = false;
      maximized_ = false;
      dx_->OnResize();
    } else if (is_resizing_) {
      // 如果用户正在拖动调整大小的滑块，我们在这里不调整缓冲区的大小，
      // 因为当用户持续拖动调整大小的滑块时，会向窗口发送一连串的 WM_SIZE 消息，
      // 对于每次从拖动调整大小滑块收到的
      // WM_SIZE消息都进行调整是没有意义的（而且会很慢）。
      // 因此，我们改为在用户完成调整窗口大小并释放调整大小滑块后，
      // 即发送 WM_EXITSIZEMOVE 消息时，再进行重置。
    } else { // API call such as SetWindowPos or mSwapChain->SetFullscreenState.
      dx_->OnResize();
    }
  }
}

void DxWindow::Reset() {
  created_ = false;
  hwnd_ = 0;
  // 重置DX状态
  dx_.reset();
}

void DxWindow::Close() {
  if (created_) {
    DestroyWindow(hwnd_);
    Reset();
  }
}

LRESULT CALLBACK DxWindow::WndProc(
    HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  switch (message) {
    // 当窗口被激活的时候，才进行游戏
  case WM_ACTIVATE:
    if (LOWORD(wParam) == WA_INACTIVE) {
      app_paused_ = true;
      ifire::g_timer.Stop();
    } else {
      app_paused_ = false;
      ifire::g_timer.Start();
    }
    return 0;
  case WM_SIZE:
    OnResize(wParam, lParam);
    return 0;
  // WM_EXITSIZEMOVE（当玩家开始拖动大小）
  case WM_ENTERSIZEMOVE:
    app_paused_ = true;
    is_resizing_ = true;
    ifire::g_timer.Stop();
    return 0;
  // WM_EXITSIZEMOVE （当玩家拖动大小结束），这时候才进行一并的计算
  case WM_EXITSIZEMOVE:
    app_paused_ = false;
    is_resizing_ = false;
    ifire::g_timer.Start();
    dx_->OnResize();
    return 0;
  case WM_DESTROY:
    // 因为是子窗口，所以不需要
    // PostQuitMessage(0);
    break;
    // 当菜单处于活动状态，并且用户按下了一个不对应任何助记符或快捷键的键时，
    // 会发送 WM_MENUCHAR 消息。
  case WM_MENUCHAR:
    // Don't beep when we alt-enter.
    return MAKELRESULT(0, MNC_CLOSE);
  // 防止窗口过小.
  case WM_GETMINMAXINFO:
    ((MINMAXINFO*)lParam)->ptMinTrackSize.x = 320;
    ((MINMAXINFO*)lParam)->ptMinTrackSize.y = 240;
    return 0;
  case WM_LBUTTONDOWN:
  case WM_MBUTTONDOWN:
  case WM_RBUTTONDOWN:
    OnMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;
  case WM_LBUTTONUP:
  case WM_MBUTTONUP:
  case WM_RBUTTONUP:
    OnMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;
  case WM_MOUSEMOVE:
    OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;
  case WM_KEYUP:
    if (wParam == VK_ESCAPE) {
      Close();
    } else if ((int)wParam == VK_F2) {
      dx_->Set4xMsaaState(!dx_->Get4xMsaaState());
    }
    return 0;
  case WM_CLOSE:
    Reset();
    break;
  }
  return DefWindowProc(hWnd, message, wParam, lParam);
}

} // namespace ifire